﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.ServiceProcess;
using System.Threading;
using Common.Logging;
using NServiceBus.Config;
using NServiceBus.Management.Config;
using NServiceBus.Management.ManagementService.Config;
using NServiceBus.Management.ManagementService.Domain;
using NServiceBus.Management.MessageHandlers;
using NServiceBus.Management.Messages.EndpointControl;
using NServiceBus.Management.Messages.Statistics;
using NServiceBus.Management.Messages.Status;
using NServiceBus.Unicast.Config;
using NServiceBus.Unicast.Subscriptions.Msmq.Config;
using NServiceBus.Unicast.Transport.Msmq.Config;
using ObjectBuilder.CastleFramework;

namespace NServiceBus.Management.ManagementService
{
    public partial class ManagementService : ServiceBase, IManagementService
    {
        public IBus Bus{ get; set;}


        private int refreshInterval = 10;
        private readonly ILog logger = LogManager.GetLogger(typeof(EndpointManager));

        private readonly Timer timer;

        private EndpointManager endpointManager;
        private IList<NumberOfMessagesDatapoint> statistics = new List<NumberOfMessagesDatapoint>();

        private readonly IDictionary<string, StatusMessage> endpointStatuses = new Dictionary<string, StatusMessage>();

        private readonly IList<Endpoint> endpoints = new List<Endpoint>();

        public void UpdateEndpointStatus(StatusMessage endpointStatus)
        {
            lock (endpointStatuses)
                endpointStatuses[endpointStatus.EndpointId] = endpointStatus;
        }

        public IList<StatusMessage> EndpointStatuses
        {
            get
            {
                lock (endpointStatuses)
                    return new List<StatusMessage>(endpointStatuses.Values);
            }
        }


        
        public ManagementService()
        {
            InitializeComponent();

            timer = new Timer(PublishStatusEvent);
        }

        public void Start(string[] args)
        {
            OnStart(args);
        }

        protected override void OnStart(string[] args)
        {
            var builder = new Builder();

            Configure.With(builder).SagasAndMessageHandlersIn(Assembly.GetExecutingAssembly(), typeof(EndpointMessageHandler).Assembly);

            new ConfigMsmqSubscriptionStorage(builder);

            new ConfigMsmqTransport(builder)
                .IsTransactional(true)
                .PurgeOnStartup(true)
                .UseXmlSerialization(false);

            new ConfigUnicastBus(builder)
                .ImpersonateSender(false)
                .SetMessageHandlersFromAssembliesInOrder(Assembly.GetExecutingAssembly(), typeof(EndpointMessageHandler).Assembly);

            new ConfigManagement(builder);

            new ConfigManagementService(builder);

            Bus = builder.Build<IBus>();

            Bus.Start();

            endpointManager = builder.Build<EndpointManager>();

            endpointManager.Start();


            timer.Change(0, refreshInterval * 1000);
        }


        private void PublishStatusEvent(object state)
        {
            //disable timer
            timer.Change(int.MaxValue, int.MaxValue);
            var watch = new Stopwatch();
            watch.Start();

            try
            {
                var statusEvent = new StatusEvent { CurrentStatus = (List<StatusMessage>)EndpointStatuses };

                Bus.Publish(statusEvent);

                logger.Debug("Event published!, Number of registered endpoints: " + statusEvent.CurrentStatus.Count);



            }
            catch (Exception ex)
            {
                logger.Error("Error when executing heartbeat", ex);
            }
            finally
            {
                watch.Stop();
            }



            long due = refreshInterval * 1000 - watch.ElapsedMilliseconds;
            due = (due < 0 ? 0 : due);

            //set timer 
            timer.Change(due, refreshInterval * 1000);
        }

        protected override void OnStop()
        {
            timer.Change(int.MaxValue, int.MaxValue);
        }

        #region IManagementService Members

        public IList<NumberOfMessagesDatapoint> GetNumberOfMessagesFor(string endpoint, Type messageType, DateTime fromTime, DateTime to)
        {
            lock (statistics)
            {
                var result = from s in statistics
                             where s.Endpoint == endpoint &&
                                    s.Type == messageType &&
                                    s.Time >= fromTime && s.Time < to
                             select s;

                return result.ToList();
            }

        }



        public void AddStatistics(NumberOfMessagesDatapoint data)
        {
            lock (statistics)
                statistics.Add(data);

        }

        #endregion

        #region IManagementService Members


        public void RegisterNewEndpoint(string id, string adress)
        {
             lock (endpoints)
             {
                 var endpoint = new Endpoint() {Id = id, Adress = adress,StatusTime = SystemTime.Now()};
                 if(endpoints.Contains(endpoint))
                 {
                     logger.Warn(string.Format("Entry for endpoint {0} already exists, possible endpoint crash and restart",id));
                     endpoints.Remove(endpoint);
                 }

                 endpoints.Add(endpoint);
             }
        }

        public IList<Endpoint> GetCurrentEndpoints()
        {
                lock (endpoints)
                    return new List<Endpoint>(endpoints);
        }


        public void PingEndpointsOlderThan(int seconds)
        {
            var deadline = SystemTime.Now().AddSeconds(-seconds);

            foreach(var endpoint in GetCurrentEndpoints())
            {
                if (endpoint.StatusTime < deadline)
                    Bus.Send(endpoint.Adress, new PingCommand());
            }
        }

     

        public void RemoveEndpointsOlderThan(int seconds)
        {
            var deadline = SystemTime.Now().AddSeconds(-seconds);

            foreach(var endpoint in GetCurrentEndpoints())
            {
                if (endpoint.StatusTime < deadline)
                {
                    lock(endpoints)
                    {
                        endpoints.Remove(endpoint);
                    }
                    logger.Warn(string.Format("Endpoint {0} has not reponded since {1} and will be removed", endpoint.Id, endpoint.StatusTime));
                }
            }
        }

        #endregion

        #region IManagementService Members


        public void UpdateStatusTimeForEndoint(string id)
        {
            lock(endpoints)
            {
                var endpoint = endpoints.Single(e => e.Id == id);

                endpoint.StatusTime = SystemTime.Now();
            }
        }

        #endregion
    }
}
